﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;

namespace Reflective
{
    /// <summary>
    /// This class holds extension methods for <see cref="ILGenerator"/>.
    /// </summary>
    public partial class ILGeneratorExtensions
    {
        /// <summary>
        /// <see cref="OpCodes.Call"/>: Call the method described by <paramref name="method"/>.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the <see cref="OpCodes.Call"/> instruction to.
        /// </param>
        /// <param name="method">
        /// The <see cref="MethodInfo"/> of the method to call.
        /// </param>
        /// <param name="optionalParameterTypes">
        /// An array of <see cref="Type"/> objects, for specifying the parameter list to use for a varargs-call. Leave
        /// <c>null</c> or empty to use a non-varargs call.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> passed to the method in <paramref name="generator"/>, to support a fluent interface.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="method"/> is <c>null</c>.</para>
        /// </exception>
        /// <remarks>
        /// See <see href="http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.call.aspx"/> for more information.
        /// </remarks>
        public static ILGenerator call(this ILGenerator generator, MethodInfo method, params Type[] optionalParameterTypes)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");
            if (method == null)
                throw new ArgumentNullException("method");

            if (optionalParameterTypes == null || optionalParameterTypes.Length == 0)
                generator.Emit(OpCodes.Call, method);
            else
                generator.EmitCall(OpCodes.Call, method, optionalParameterTypes);

            return generator;
        }

        /// <summary>
        /// <see cref="OpCodes.Calli"/>: Calls the method indicated on the evaluation stack (as a pointer to an entry point) with arguments described by a calling convention.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the opcode through.
        /// </param>
        /// <param name="unmanagedCallConv">
        /// The calling convention to use.
        /// </param>
        /// <param name="returnType">
        /// The <see cref="Type"/> object for the return value from the method.
        /// </param>
        /// <param name="parameterTypes">
        /// An array of <see cref="Type"/> objects for the arguments to pass to the method.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="returnType"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="parameterTypes"/> is <c>null</c>.</para>
        /// </exception>
        /// <remarks>
        /// See <see href="http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.calli.aspx"/> for more information.
        /// </remarks>
        public static ILGenerator calli(this ILGenerator generator, CallingConvention unmanagedCallConv, Type returnType, Type[] parameterTypes)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");
            if (returnType == null)
                throw new ArgumentNullException("returnType");
            if (parameterTypes == null)
                throw new ArgumentNullException("parameterTypes");

            generator.EmitCalli(OpCodes.Calli, unmanagedCallConv, returnType, parameterTypes);

            return generator;
        }

        /// <summary>
        /// <see cref="OpCodes.Calli"/>: Calls the method indicated on the evaluation stack (as a pointer to an entry point) with arguments described by a calling convention.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the opcode through.
        /// </param>
        /// <param name="callingConvention">
        /// The calling convention to use.
        /// </param>
        /// <param name="returnType">
        /// The <see cref="Type"/> object for the return value from the method.
        /// </param>
        /// <param name="parameterTypes">
        /// An array of <see cref="Type"/> objects for the arguments to pass to the method.
        /// </param>
        /// <param name="optionalParameterTypes">
        /// An array of <see cref="Type"/> objects for the varargs support.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="returnType"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="parameterTypes"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="optionalParameterTypes"/> is <c>null</c>.</para>
        /// </exception>
        /// <remarks>
        /// See <see href="http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.calli.aspx"/> for more information.
        /// </remarks>
        public static ILGenerator calli(this ILGenerator generator, CallingConventions callingConvention, Type returnType, Type[] parameterTypes, Type[] optionalParameterTypes)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");
            if (returnType == null)
                throw new ArgumentNullException("returnType");
            if (parameterTypes == null)
                throw new ArgumentNullException("parameterTypes");
            if (optionalParameterTypes == null)
                throw new ArgumentNullException("optionalParameterTypes");

            generator.EmitCalli(OpCodes.Calli, callingConvention, returnType, parameterTypes, optionalParameterTypes);

            return generator;
        }

        /// <summary>
        /// <see cref="OpCodes.Callvirt"/>: Calls a method <paramref name="method"/> associated with an object.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the <see cref="OpCodes.Callvirt"/> instruction to.
        /// </param>
        /// <param name="method">
        /// The <see cref="MethodInfo"/> of the virtual method to call.
        /// </param>
        /// <param name="optionalParameterTypes">
        /// An array of <see cref="Type"/> objects, for specifying the parameter list to use for a varargs-call. Leave
        /// <c>null</c> or empty to use a non-varargs call.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> passed to the method in <paramref name="generator"/>, to support a fluent interface.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="method"/> is <c>null</c>.</para>
        /// </exception>
        /// <remarks>
        /// See <see href="http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.callvirt.aspx"/> for more information.
        /// </remarks>
        public static ILGenerator callvirt(this ILGenerator generator, MethodInfo method, params Type[] optionalParameterTypes)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");
            if (method == null)
                throw new ArgumentNullException("method");

            if (optionalParameterTypes == null || optionalParameterTypes.Length == 0)
                generator.Emit(OpCodes.Callvirt, method);
            else
                generator.EmitCall(OpCodes.Callvirt, method, optionalParameterTypes);

            return generator;
        }

        /// <summary>
        /// Pushes the integer value of 0 onto the evaluation stack as an int32, using the smallest instruction possible.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the <see cref="OpCodes.Ldc_I4_0"/> instruction to.
        /// </param>
        /// <param name="value">
        /// The <see cref="Int32"/> value to load onto the stack.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> passed to the method in <paramref name="generator"/>, to support a fluent interface.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// </exception>
        public static ILGenerator ldc_i4_opt(this ILGenerator generator, int value)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");

            switch (value)
            {
                case -1:
                    return generator.ldc_i4_m1();

                case 0:
                    return generator.ldc_i4_0();

                case 1:
                    return generator.ldc_i4_1();

                case 2:
                    return generator.ldc_i4_2();

                case 3:
                    return generator.ldc_i4_3();

                case 4:
                    return generator.ldc_i4_4();

                case 5:
                    return generator.ldc_i4_5();

                case 6:
                    return generator.ldc_i4_6();

                case 7:
                    return generator.ldc_i4_7();

                case 8:
                    return generator.ldc_i4_8();
            }

            if (value >= 0 && value <= 255)
                return generator.ldc_i4_s((byte)value);

            return generator.ldc_i4(value);
        }

        /// <summary>
        /// Loads the specified constant onto the evaluation stack using the correct
        /// Ldc_X opcode. See remarks for supported types.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the opcode through.
        /// </param>
        /// <param name="value">
        /// The value to load onto the evaluation stack.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="InvalidOperationException">
        /// <para><paramref name="value"/> contains a value of an unsupported type.</para>
        /// </exception>
        /// <remarks>
        /// The supported types are <see cref="Byte"/>, <see cref="SByte"/>, <see cref="UInt16"/>, <see cref="Int16"/>,
        /// <see cref="UInt32"/>, <see cref="int"/>, <see cref="UInt64"/>, <see cref="Int64"/>, <see cref="Single"/>,
        /// <see cref="Double"/>, <see cref="Boolean"/>, nullable versions of all those, and <c>null</c>, as well as <see cref="string"/>.
        /// </remarks>
        public static ILGenerator ldc(this ILGenerator generator, object value)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");

            if (value == null)
                return generator.ldnull();

            // Let's get rid of Nullable<T> wrapper for simplicity
            if (value.GetType().IsGenericType)
            {
                if (value.GetType().GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    value = Convert.ChangeType(value, value.GetType().GetGenericArguments()[0], CultureInfo.CurrentCulture);
                }
            }

            int? smallValue = null;
            switch (value.GetType().FullName)
            {
                case "System.String":
                    return generator
                        .ldstr((string)value);

                case "System.Boolean":
                    smallValue = (bool)value ? 1 : 0;
                    break;

                case "System.Byte":
                    if ((byte)value >= 0 && (byte)value <= 127)
                        smallValue = (byte)value;
                    else
                        return generator.ldc_i4((byte)value);
                    break;

                case "System.SByte":
                    if ((sbyte)value >= -128 && (sbyte)value <= 127)
                        smallValue = (sbyte)value;
                    else
                        return generator.ldc_i4((sbyte)value);
                    break;

                case "System.Int16":
                    if ((short)value >= -128 && (short)value <= 127)
                        smallValue = (short)value;
                    else
                        return generator.ldc_i4((short)value);
                    break;

                case "System.UInt16":
                    if ((ushort)value >= 0 && (ushort)value <= 127)
                        smallValue = (ushort)value;
                    else
                        return generator.ldc_i4((ushort)value);
                    break;

                case "System.Int32":
                    if ((int)value >= -128 && (int)value <= 127)
                        smallValue = (int)value;
                    else
                        return generator.ldc_i4((int)value);
                    break;

                case "System.UInt32":
                    if ((uint)value >= 0 && (uint)value <= 127)
                        smallValue = (int)(uint)value;
                    else
                        return generator.ldc_i4((int)(uint)value);
                    break;

                case "System.Int64":
                    if ((long)value >= -128 && (long)value <= 127)
                        smallValue = (int)(long)value;
                    else
                        return generator.ldc_i8((long)value);
                    break;

                case "System.UInt64":
                    if ((ulong)value >= 0 && (ulong)value <= 127)
                        smallValue = (int)(ulong)value;
                    else
                        return generator.ldc_i8((long)(ulong)value);
                    break;
            }

            if (smallValue.HasValue)
            {
                switch (smallValue.Value)
                {
                    case -1:
                        return generator.ldc_i4_m1();

                    case 0:
                        return generator.ldc_i4_0();

                    case 1:
                        return generator.ldc_i4_1();

                    case 2:
                        return generator.ldc_i4_2();

                    case 3:
                        return generator.ldc_i4_3();

                    case 4:
                        return generator.ldc_i4_4();

                    case 5:
                        return generator.ldc_i4_5();

                    case 6:
                        return generator.ldc_i4_6();

                    case 7:
                        return generator.ldc_i4_7();

                    case 8:
                        return generator.ldc_i4_8();

                    default:
                        return generator.ldc_i4(smallValue.Value);
                }
            }

            if (value.GetType() == typeof(float))
                return generator.ldc_r4((float)value);
            if (value.GetType() == typeof(double))
                return generator.ldc_r8((double)value);

            if (value.GetType().IsEnum)
            {
                switch (Enum.GetUnderlyingType(value.GetType()).FullName)
                {
                    case "System.Int32":
                        return generator.ldc((int)value);

                    case "System.UInt32":
                        return generator.ldc((uint)value);

                    case "System.Int16":
                        return generator.ldc((short)value);

                    case "System.UInt16":
                        return generator.ldc((ushort)value);

                    case "System.Int64":
                        return generator.ldc((long)value);

                    case "System.UInt64":
                        return generator.ldc((ulong)value);

                    case "System.Byte":
                        return generator.ldc((byte)value);

                    case "System.SByte":
                        return generator.ldc((sbyte)value);
                }
            }

            throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Unsupported type '{0}' passed to method ldc", value.GetType().FullName));
        }

        /// <summary>
        /// Generates the default value for the type, pushes that value onto the evaluation stack, and emits a
        /// return instruction. See the <see cref="ldc"/> method for a list of supported types.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the opcode through.
        /// </param>
        /// <param name="type">
        /// The type of the value to push onto the evaluation stack, can be <c>null</c> if the
        /// method has no return type (only the ret instruction will be emitted).
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="InvalidOperationException">
        /// <para><paramref name="type"/> specifies an unsupported type.</para>
        /// </exception>
        public static ILGenerator ret_default(this ILGenerator generator, Type type)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");

            if (type == null)
                return generator.ret();

            switch (type.FullName)
            {
                case "System.Void":
                    return generator.ret();

                case "System.Int32":
                case "System.Int16":
                case "System.Int64":
                case "System.UInt32":
                case "System.UInt16":
                case "System.UInt64":
                case "System.Byte":
                case "System.SByte":
                case "System.Char":
                case "System.Boolean":
                case "System.Double":
                case "System.Single":
                    return generator
                        .ldc(Activator.CreateInstance(type))
                        .ret();
            }

            // Have to embed a call to the type constructor
            ConstructorInfo ci = type.GetConstructor(new Type[0]);
            return generator
                .newobj(ci)
                .ret();
        }

        /// <summary>
        /// Figures out whether the specified method is a virtual or a non-virtual method, and emits the right method
        /// call opcodes.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the opcode through.
        /// </param>
        /// <param name="method">
        /// The <see cref="MethodInfo"/> to emit as an argument to the opcode.
        /// </param>
        /// <param name="varargsParameterTypes">
        /// Any optional varargs parameter types.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="method"/> is <c>null</c>.</para>
        /// </exception>
        public static ILGenerator call_smart(this ILGenerator generator, MethodInfo method, params Type[] varargsParameterTypes)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");
            if (method == null)
                throw new ArgumentNullException("method");

            if (method.IsVirtual)
            {
                if (varargsParameterTypes.Length > 0)
                    return generator.callvirt(method, varargsParameterTypes);

                return generator.callvirt(method);
            }

            if (varargsParameterTypes.Length > 0)
                return generator.call(method, varargsParameterTypes);

            return generator.call(method);
        }

        /// <summary>
        /// Marks the location in the instruction stream where the label should have its
        /// target.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the opcode through.
        /// </param>
        /// <param name="label">
        /// The label to emit the target for.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="label"/> is <c>null</c>.</para>
        /// </exception>
        public static ILGenerator mark_label(this ILGenerator generator, Label label)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");

            generator.MarkLabel(label);
            return generator;
        }

        /// <summary>
        /// Loads the specified local onto the stack, by index, emitting the optimal
        /// index instruction.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the opcode through.
        /// </param>
        /// <param name="index">
        /// The index of the local to load.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <para><paramref name="index"/> must be 65535 or less.</para>
        /// </exception>
        public static ILGenerator ldloc_opt(this ILGenerator generator, int index)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");
            if (index > UInt16.MaxValue)
                throw new ArgumentOutOfRangeException(String.Format(CultureInfo.InvariantCulture, "index can only be within 0..{0}", UInt16.MaxValue));

            switch (index)
            {
                case 0:
                    return generator.ldloc_0();
                case 1:
                    return generator.ldloc_1();
                case 2:
                    return generator.ldloc_2();
                case 3:
                    return generator.ldloc_3();
            }

            if (index <= Byte.MaxValue)
                return generator.ldloc_s((byte)index);

            return generator.ldloc((short)index);
        }

        /// <summary>
        /// Emits one of the <see cref="OpCodes.Ldarg"/> opcodes, depending
        /// on the index.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the opcode through.
        /// </param>
        /// <param name="index">
        /// The <see cref="UInt16"/> to emit as an argument to the opcode.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <para><paramref name="index"/> is out of range</para>
        /// </exception>
        public static ILGenerator ldarg_opt(this ILGenerator generator, int index)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");
            if (index > UInt16.MaxValue)
                throw new ArgumentOutOfRangeException(String.Format(CultureInfo.InvariantCulture, "index can only be within 0..{0}", UInt16.MaxValue));

            switch (index)
            {
                case 0:
                    return generator.ldarg_0();
                case 1:
                    return generator.ldarg_1();
                case 2:
                    return generator.ldarg_2();
                case 3:
                    return generator.ldarg_3();
            }

            if (index <= Byte.MaxValue)
                return generator.ldarg_s((byte)index);

            return generator.ldarg((short)index);
        }

        /// <summary>
        /// Emits one of the <see cref="OpCodes.Ldarg"/> opcodes, depending
        /// on the index, one for each index.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the opcode through.
        /// </param>
        /// <param name="indices">
        /// An array of <see cref="UInt16"/> indices to emit as an argument to the opcodes.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// </exception>
        public static ILGenerator ldargs(this ILGenerator generator, params int[] indices)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");

            foreach (int index in indices)
                generator.ldarg_opt(index);

            return generator;
        }

        /// <summary>
        /// Emits one of the <see cref="OpCodes.Ldarg"/> opcodes, depending
        /// on the index, one for each index.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the opcode through.
        /// </param>
        /// <param name="indices">
        /// A collection of <see cref="UInt16"/> indices to emit as an argument to the opcodes.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// </exception>
        public static ILGenerator ldargs(this ILGenerator generator, IEnumerable<int> indices)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");

            return generator.ldargs(indices.ToArray());
        }

        /// <summary>
        /// Emits the <see cref="OpCodes.Ldarg_0"/> opcode, to
        /// load the "this" reference.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the opcode through.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// </exception>
        public static ILGenerator ld_this(this ILGenerator generator)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");

            return generator.ldarg_0();
        }

        /// <summary>
        /// Evaluates the <see cref="Boolean"/> expression, and if <c>true</c>,
        /// executes the <paramref name="ifThen"/> delegate to add
        /// the necessary IL code to the stream.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the opcode through.
        /// </param>
        /// <param name="expression">
        /// The <see cref="Boolean"/> expression used to determine if the
        /// code is to be added or not.
        /// </param>
        /// <param name="ifThen">
        /// The delegate to call if <paramref name="expression"/> is <c>true</c>.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// </exception>
        public static ILGenerator EmitIf(this ILGenerator generator, bool expression, Action<ILGenerator> ifThen)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");
            if (ifThen == null)
                throw new ArgumentNullException("ifThen");

            return EmitIf(generator, expression, ifThen, null);
        }

        /// <summary>
        /// Evaluates the <see cref="Boolean"/> expression, and if <c>true</c>,
        /// calls the <paramref name="ifThen"/> delegate to add
        /// the necessary IL code to the stream; or if <c>false</c>,
        /// calls the <paramref name="ifElse"/> delegate.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the opcode through.
        /// </param>
        /// <param name="expression">
        /// The <see cref="Boolean"/> expression used to determine if the
        /// code is to be added or not.
        /// </param>
        /// <param name="ifThen">
        /// The delegate to call if <paramref name="expression"/> is <c>true</c>.
        /// Can be <c>null</c> if no action is to be taken on <c>true</c>.
        /// </param>
        /// <param name="ifElse">
        /// The delegate to call if <paramref name="expression"/> is <c>false</c>.
        /// Can be <c>null</c> if no action is to be taken on <c>false</c>.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// </exception>
        public static ILGenerator EmitIf(this ILGenerator generator, bool expression, Action<ILGenerator> ifThen, Action<ILGenerator> ifElse)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");

            if (expression)
            {
                if (ifThen != null)
                    ifThen(generator);
                return generator;
            }

            if (ifElse != null)
                ifElse(generator);

            return generator;
        }

        /// <summary>
        /// Calls the specified <see cref="Action{ILGenerator,T}"/> delegate for
        /// each element of the collection of source values.
        /// </summary>
        /// <typeparam name="T">
        /// The type of element to emit the body for.
        /// </typeparam>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the opcode through.
        /// </param>
        /// <param name="source">
        /// The source values to call the action for.
        /// </param>
        /// <param name="body">
        /// The action to call for each value in the <paramref name="source"/>.
        /// objects.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="source"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="body"/> is <c>null</c></para>
        /// </exception>
        public static ILGenerator EmitFor<T>(this ILGenerator generator, IEnumerable<T> source, Action<ILGenerator, T> body)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");
            if (source == null)
                throw new ArgumentNullException("source");
            if (body == null)
                throw new ArgumentNullException("body");

            foreach (T element in source)
                body(generator, element);
            return generator;
        }

        /// <summary>
        /// Declares a local variable, which will start with an index of 0 for
        /// the first variable and increment by 1 each time.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to define the local variable for.
        /// </param>
        /// <param name="type">
        /// The type of the variable.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="type"/> is <c>null</c>.</para>
        /// </exception>
        public static ILGenerator variable(this ILGenerator generator, Type type)
        {
            generator.DeclareLocal(type);
            return generator;
        }

        /// <summary>
        /// Declares a local variable, which will start with an index of 0 for
        /// the first variable and increment by 1 each time.
        /// </summary>
        /// <typeparam name="T">
        /// The type of the variable.
        /// </typeparam>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to define the local variable for.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// </exception>
        public static ILGenerator variable<T>(this ILGenerator generator)
        {
            return generator.variable(typeof(T));
        }

        /// <summary>
        /// Creates a new object or a new instance of a value type, pushing an object reference (type O) onto the evaluation stack. (<see cref="OpCodes.Newobj"/>).
        /// </summary>
        /// <typeparam name="T">
        /// The type of object to construct.
        /// </typeparam>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the opcode through.
        /// </param>
        /// <param name="parameterTypes">
        /// The parameter types to find a matching constructor for.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// </exception>
        public static ILGenerator newobj<T>(this ILGenerator generator, params Type[] parameterTypes)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");

            if (parameterTypes == null)
                parameterTypes = new Type[0];

            return generator.newobj(typeof(T).GetConstructor(parameterTypes));
        }

        /// <summary>
        /// Executes the <paramref name="codeBlock"/>, passing in the specified
        /// <paramref name="generator"/>.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the using clause through.
        /// </param>
        /// <param name="codeBlock">
        /// The block of code to execute.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="codeBlock"/> is <c>null</c>.</para>
        /// </exception>
        public static ILGenerator EmitBlock(this ILGenerator generator, Func<ILGenerator, ILGenerator> codeBlock)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");
            if (codeBlock == null)
                throw new ArgumentNullException("codeBlock");

            return codeBlock(generator);
        }

        /// <summary>
        /// Emits a <c>try { ... }</c> clause.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the try clause through.
        /// </param>
        /// <param name="codeBlock">
        /// The block of code to execute inside the try clause.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="codeBlock"/> is <c>null</c>.</para>
        /// </exception>
        public static ILGenerator EmitTry(this ILGenerator generator, Func<ILGenerator, ILGenerator> codeBlock)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");
            if (codeBlock == null)
                throw new ArgumentNullException("codeBlock");

            generator.BeginExceptionBlock();
            generator.EmitBlock(codeBlock);
            generator.EndExceptionBlock();
            return generator;
        }

        /// <summary>
        /// Emits a <c>catch (T) { ... }</c> block.
        /// </summary>
        /// <typeparam name="T">
        /// The type of exception to catch.
        /// </typeparam>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the catch block through.
        /// </param>
        /// <param name="codeBlock">
        /// The block of code to execute inside the catch block.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="codeBlock"/> is <c>null</c>.</para>
        /// </exception>
        public static ILGenerator EmitCatch<T>(this ILGenerator generator, Func<ILGenerator, LocalBuilder, ILGenerator> codeBlock)
            where T : Exception
        {
            if (generator == null)
                throw new ArgumentNullException("generator");
            if (codeBlock == null)
                throw new ArgumentNullException("codeBlock");

            generator.BeginCatchBlock(typeof(T));
            LocalBuilder ex = generator.DeclareLocal(typeof(Exception));
            generator.stloc(ex);
            return codeBlock(generator, ex);
        }

        /// <summary>
        /// Emits a <c>finally { ... }</c> clause.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the using clause through.
        /// </param>
        /// <param name="codeBlock">
        /// The block of code to execute inside the using clause.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="codeBlock"/> is <c>null</c>.</para>
        /// </exception>
        public static ILGenerator EmitFinally(this ILGenerator generator, Func<ILGenerator, ILGenerator> codeBlock)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");
            if (codeBlock == null)
                throw new ArgumentNullException("codeBlock");

            generator.BeginFinallyBlock();
            return generator.EmitBlock(codeBlock);
        }

        /// <summary>
        /// Implements a <c>using(topOfstack){}</c> section.
        /// </summary>
        /// <param name="generator">
        /// The <see cref="ILGenerator"/> to emit the using clause through.
        /// </param>
        /// <param name="codeBlock">
        /// The block of code to execute inside the using clause.
        /// </param>
        /// <returns>
        /// The <see cref="ILGenerator"/> instance passed through <paramref name="generator"/>, for method chaining.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="generator"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="codeBlock"/> is <c>null</c>.</para>
        /// </exception>
        public static ILGenerator EmitUsing(this ILGenerator generator, Func<ILGenerator, ILGenerator> codeBlock)
        {
            if (generator == null)
                throw new ArgumentNullException("generator");
            if (codeBlock == null)
                throw new ArgumentNullException("codeBlock");

            LocalBuilder disposable = generator.DeclareLocal(typeof(IDisposable));
            Label exit = generator.DefineLabel();
            return generator
                .castclass(typeof(IDisposable))
                .stloc(disposable)
                .EmitTry(t => t
                    .EmitBlock(codeBlock)
                    .EmitFinally(f => f
                        .ldloc(disposable)
                        .ldnull()
                        .ceq()
                        .brtrue_s(exit)
                        .ldloc(disposable)
                        .call_smart(typeof(IDisposable).GetMethod("Dispose"))
                        .mark_label(exit)));
        }
    }
}